TailsConfig.registerFormat({
  name: 'hcard',
  topNodeClass: 'vcard',

  simpleValueClasses: [
      "fn", "n", "given-name", "family-name", "title", "org"
      , "locality", "region", "street-address", "postal-code"
      , "country-name", "nickname", "bday", "organization-name"
      , "organization-unit", "role", "class", "key", "mailer"
      , "rev", "tz", "sort-string"],

  pluralValueClasses: ["additional-name", "honorific-prefix"
      , "honorific-suffix", "note"],

  urlValueClasses: ["logo", "photo"],
  pluralURLValueClasses: ["url"],

  emailValueClasses: ["email"],

  embeddedFormatClasses: { geo: 'geo' },

  genericLabelAttributes: ["fn", "n", "org", "email"],
  genericURLAttributes: ["url"],
  genericIconAttributes: ["logo", "photo"],
  genericDescriptionAttributes: ["title", "org"],

  customBindings: {
    adr: function(node, object) {
      var format = FormatBase.createFormat({
        simpleValueClasses: [
          "value", "post-office-box", "extended-address"
          , "locality", "region", "postal-code", "country-name"
        ],
        pluralValueClasses: [ "street-address" ],
        customBindings: {
          type: function(node, object) {
            var val = FormatBase.toText(node);
            if (!object.type) object.type = val;
            if (!object.types) object.types = [];
            object.types.push(val);
          }
        }});
      var adr = format.parseObject(node);

      if (!object.adr) {
        object.adr = adr;
        object.address = adr;
        object.addresses = [];
        
        var atts = ["post-office-box", "extended-address"
          , "street-address", "street-addresses", "locality"
          , "region", "postal-code", "country-name"];
        for (var i=0; i<atts.length; i++) {
          object[atts[i]] = adr[atts[i]];
        }
      }
      object.addresses.push(adr);
      return false;
    },
    tel: function(node, object) {
      var tel = FormatBase.createFormat({
          pluralValueClasses: ["type"] }).parseObject(node);

      tel.value = FormatBase.toText(node);

      if (!object.tel) object.tel = new Array();
      object.tel.push(tel);
      return true;
    },
    category: function(node, object) {
      var tag = FormatBase.toTag(node);
      if (!tag) tag = FormatBase.toText(node);
      if (!object.category) object.category = tag;
      if (!object.categories) object.categories = [];
      object.categories.push(tag);
    },
    uid: function(node, object) {
      if (!object.uid) {
        var uid = FormatBase.toURL(node);
        if (!uid)
          uid = FormatBase.toText(node);
        object.uid = uid;
      }
    }
  },

  objectMethods: {
    toAddressString: function() {
      return FormatBase.commaJoin(this['street-address'],this.toLocalityRegionPostalCodeAndCountryString());
    },
    toStreetAddressLocalityRegionAndPostalCodeString: function() {
      return FormatBase.commaJoin(this['street-address'],this.toLocalityRegionAndPostalCodeString());
    },
    toLocalityAndRegionString: function() {
      return FormatBase.commaJoin(this.locality,this.region);
    },
    toLocalityRegionAndPostalCodeString: function() {
      var s = this.toLocalityAndRegionString();
      if (this['postal-code'])
        s += (s.length > 0 ? (this.region ? " " : ", "): "") + this['postal-code'];
      return s;
    },
    toLocalityRegionAndCountryString: function() {
      return FormatBase.commaJoin(this.toLocalityAndRegionString(), this['country-name']);
    },
    toLocalityRegionPostalCodeAndCountryString: function() {
      return FormatBase.commaJoin(this.toLocalityRegionAndPostalCodeString(), this['country-name']);
    },
    /* return the vcard contents for this hcard. */
    toVCard: function() {
      var helper = new ExportHelper("VCARD");
      
      // ADR
      if (this.addresses) {
        var order = "dom,intl,postal,parcel,home,work,pref".split(",");
        for (var i=0; i<this.addresses.length; i++) {
          helper.compList(this.addresses[i], "post-office-box", "extended-address",
              "street-addresses", "locality", "region", "postal-code",
              "country-name").utf8().typed(order).add("ADR");
        }
      }

      // BDAY, GEO 
      helper.prop(this, 'bday').add();
      helper.compList(this.geo, 'latitude', 'longitude').add("GEO");
      
      // CLASS, EMAIL, FN, LOGO, REV, SORT-STRING
      helper.prop(this, 'class').utf8().add();
      helper.prop(this, 'email').add();
      helper.prop(this, 'fn').utf8().add();
      helper.prop(this, 'logo').resource().add();
      helper.prop(this, 'rev').add();
      helper.prop(this, 'sort-string').utf8().add();

      // N
      var n = helper.compList(this, "family-name", "given-name",
          "additional-names", "honorific-prefixes", "honorific-suffixes");
      if (n.value == ";;;;") {
        if (this.fn) {
          var fn = this.fn.split(" ");
          if (fn.length > 1) {
            // handle formal name representation
            if (fn[0].charAt(fn[0].length-1) == ',') {
              var tmp = fn[1];
              fn[1] = fn[0].substring(0, fn[0].length-1);
              fn[0] = tmp;
            
            // handle first initial representation
            } else if (fn[1].length == 1 || fn[1].length == 2 && fn[1].charAt(1) == '.') {
              var tmp = fn[1];
              fn[1] = fn[0];
              fn[0] = tmp;
            }

            if (fn.length == 2) {
              helper.compList([fn[1], fn[0], "", "", ""]).utf8().add("N");
            
            /* These conditions causes tests to fail
            } else if (parts.length == 3) {
              helper.compList([fn[2], fn[0], fn[1], "", ""]).utf8().add("N");
            } else if (parts.length == 4) {
              helper.compList([fn[3], fn[1], fn[2], fn[0], ""]).utf8().add("N"); */
            } else {
              n.add("N");
            }
          } else {
            n.add("N");
          }
        } else {
          n.add("N");
        }
      } else {
        n.utf8().add("N");
      }
      
      // NAME, NICKNAME, PHOTO
      helper.memo(null, "NAME", this.source.title).add();
      helper.prop(this, "nickname").utf8().add();
      helper.prop(this, "photo").resource().add();
      
      // NOTE
      helper.prop(this, "notes").utf8().add("NOTE");
      
      // ORG
      if (this['organization-name'] || this['organization-unit'])
        helper.compList(this, 'organization-name', 'organization-unit', "").utf8().add("ORG");
      else
        helper.prop(this, 'org').utf8().add();
      
      // PRODID, ROLE, SOURCE, URL, TITLE, TZ, UID
      helper.memo(null, 'PRODID', TailsConfig.prodId).add();
      helper.prop(this, "role").utf8().add();
      helper.memo(null, 'SOURCE', this.source.url).add();
      helper.array(this, 'urls').typed().add("URL");
      helper.prop(this, 'title').utf8().add();
      helper.prop(this, "tz").utf8().add();
      helper.prop(this, "uid").add();
      
      // CATEGORIES
      helper.commaList(this, 'categories').utf8().add();
      
      // TEL
      helper.array(this, 'tel', 'value')
          .typed("home,work,pref,voice,fax,msg,cell,pager,bbs,car,isdn,video,pcs".split(","))
          .add();
      
      helper.memo(null, "VERSION", "3.0").add();
      return helper.toString();
    }
  },

  _getDescription: function(object) {
    if (object.fn == object.org || object.generic.label == object.org) {
      return object.toAddressString();
    }
  },
  _parseFinished: function(node, object) {
    if (!object.generic.label && node.tagName.toLowerCase() == "address") {
      if (node.ownerDocument.location.hostname)
        object.fn = node.ownerDocument.location.hostname;
      else
        object.fn = node.ownerDocument.title;
      object.generic.label = object.fn;
    }
  },
  end: null
});
